module jinji.middleware.web;

import dast.ws,
jinji.config,
tame.builtins;

/++ 设置响应头
Params:
	client = 客户端
	status = 状态码
	type = MIME内容类型
+/
void setHeader(WSClient client, Status status = Status.OK,
	in char[] type = "text/html; charset=UTF-8") {

	client.header = cast(const(ubyte)[])"HTTP/1.1 ";
	client.header ~= status;
	if (type.length) {
		client.header ~= "\r\nContent-Type: ";
		client.header ~= type;
	}
	client.header ~= "\r\n";
	if (status.length == Status.OK.length)
		client.header ~= "Access-Control-Allow-Origin: *\r\n" ~
			"Access-Control-Allow-Methods: GET\r\n";
}

/// 获取响应头
@property ref header(WSClient client) => client.frame.data;

/// 发送分块数据
void sendChunked(WSClient client, in void[] data) {
	if (!client.frame.fin) {
		client.write(client.header);
		client.write("Transfer-Encoding: chunked\r\n\r\n");
		client.header = [];
		client.frame.fin = true;
	}
	char[18] b = void;
	auto p = intToHex(b.ptr, data.length);
	p[0] = '\r';
	p[1] = '\n';
	client.write(b[0 .. p - b.ptr + 2] ~ cast(const char[])data ~ "\r\n");
}

/// 整数转十六进制
private auto intToHex(return char* buf, size_t value) @trusted {
	char* p = buf;
	for (;;) {
		const n = cast(int)value & 0xf ^ '0';
		*p++ = cast(char)(n < 58 ? n : n + 39);
		if (value < 16)
			break;
		value >>= 4;
	}
	for (char* i = buf, j = p - 1; i < j; i++, j--) {
		char t = *i;
		*i = *j;
		*j = t;
	}
	return p;
}

unittest {
	char[16] buf = void;
	auto s = buf[];
	auto p = buf.ptr;
	assert(intToHex(p, 0) - p == 1);
	assert(s[0 .. 1] == "0", s);
	assert(intToHex(p, 0xff) - p == 2);
	assert(s[0 .. 2] == "ff", s);
	assert(intToHex(p, 0x12345678) - p == 8, s);
	assert(s[0 .. 8] == "12345678", s);
	assert(intToHex(p, size_t.max) - p == 16, s);
	assert(s == "ffffffffffffffff", s);
}

/// 处理请求
void handle(WSServer, WSClient client, in Request, scope NextHandler next) {
	client.header = [];
	client.frame.fin = false;
	next();
	client.sendChunked([]);
	client.flush();
}

/// 处理HTTP请求
void serve(WSServer, WSClient client, in Request req, scope NextHandler next) {
	import dast.net.mimetypes,
	std.file,
	std.stdio,
	std.uri,
	tame.buffer,
	tame.io.path,
	tame.string;

	auto status = Status.OK;
	const(char)[] url = req.path;
	if (likely(url.startsWith('/'))) { // TODO
		auto p = url.lastIndexOf('/');
		try {
			url = url[p + 1 .. $];
			const file = url.length ? decode(url) : "index.html";
			p = file.indexOf('.');
			const(char)[] type = p == -1 ? null : getMimeType(file[p .. $]);
			if (!type.length) {
				const i = file.lastIndexOf('.');
				if (i != -1)
					type = getMimeType(file[i .. $]);
			}
			client.setHeader(status, type);
			StringSink s;
			s ~= dirName(thisExePath);
			s ~= '\\';
			s ~= C.uiPath;
			s ~= '\\';
			s ~= file;
			const path = cast(string)s[];
			if (path.exists) {
				auto f = File(path);
				ubyte[1024] buf = void;
				while (true) {
					const data = f.rawRead(buf);
					if (!data.length)
						break;
					client.sendChunked(data);
				}
			} else
				client.setHeader(Status.NotFound);
		} catch (Exception e) {
			client.setHeader(Status.Error, "text/plain");
			client.sendChunked(e.toString());
		}
	}
	next();
}
